package parser;

import game.MoveExecutionList;

import java.awt.Point;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

import chess.Board;
import chess.Player;
import chess.Tile;

import command.AttackCommand;
import command.CastleCommand;
import command.Command;
import command.EnPassantAttackCommand;
import command.KnightsTourMoveCommand;
import command.MoveCommand;
import command.NoSecondaryCommand;
import command.PiecePlaceCommand;
import command.PromoteCommand;
/**
 * This class will control the imports and export of the moves made by 2 players. This class
 * is also the save and load class. It exports and import in a file type of .cg
 *
 */
public class ImportAndExportMoveList extends ChessMoveParser implements ParserConstants,ParserErrorConstants{
	public static final String EXTENSION = "cg";
		
	public static final String TWO_COMMAND_SPLIT = "with";
	public static final String COMMAND_INFO_SPLIT = ":";
	public static final String INFO_ACTION_SPLIT = "to";
	
	
	/**
	 * Creates a new Import and Export Move List object
	 * @param board A board object used for reference when parsing the moves
	 * @param players The list of current players
	 */
	public ImportAndExportMoveList(Board board,ArrayList<Player> players){
		super(board,players);
	}
	
	/**
	 * This method will write to a file the move list 
	 * @return Returns true if the writing was successful
	 */
	public boolean writeFile(){
		if(outputFile == null){
			return false;
		}
		FileWriter output = null;
		BufferedWriter writer = null;
		try {
			output = new FileWriter(outputFile);
			writer = new BufferedWriter(output);
			writer.write(board.getGame().getMoveExecutionList().exportList());
		} catch (IOException e){
			errorCode = ParserErrorConstants.FILE_IO_ERROR;
			return false;
		} finally{
			if(writer != null){
				try {
					writer.flush();
					writer.close();
					output.close();
					return true;
				} catch (IOException e) {
					errorCode = ParserErrorConstants.FILE_UNABLE_TO_OPEN;
					return false;
				}
			}
		}
		return false;
	}
	/**
	 * This method will parse a file only if a file has been choosen
	 * @return Returns a list of moves that was created when parsing the file
	 */
	public MoveExecutionList parseFile(){
		if(file == null){
			return null;
		}
		try {
			FileInputStream input = new FileInputStream(file);
			BufferedReader reader = new BufferedReader(new InputStreamReader(input));
			String s = "";
			while(s != null){
				s = reader.readLine();
				if(s != null){
					if(!s.equals("\n") || !s.equals("") || !s.equals(" ")){
						Command command = stringToCommand(s);
						if(command == null){
							return null;
						}
						listOfMoves.add(command);
					}
				}
			}
			reader.close();
			input.close();
		} catch (FileNotFoundException e) {
			errorCode = ParserErrorConstants.FILE_NOT_FOUND_ERROR;
			return null;
		} catch (IOException e){
			errorCode = ParserErrorConstants.FILE_UNABLE_TO_OPEN;
			return null;
		} 
		return listOfMoves;

	}
	/**
	 * This will take line a turn it into a command. The format of the command is as follows. This
	 * method will also execute the command.
	 * <pre>
	 * Command:Action with Command:Action
	 * <br>
	 * Command = Attack/Move/Castle/EnPassant/NONE/Promote
	 * <br>
	 * Action = Point_1 to Point_2/Point_1 to PieceName
	 * <br>
	 * Point = (x,y)
	 * <br>
	 * PieceName = Queen/Rook/Knight/Bishop
	 * </pre>
	 * @param line The string to be parsed
	 * @return Returns a command from the parsed string. Returns null if there was an error
	 */
	public Command stringToCommand(String line){
		line = line.replaceAll(" ", "");
		line = line.replaceAll("\t", "");
		line = line.replaceAll("\n", "");
		String commands[] = line.split(ImportAndExportMoveList.TWO_COMMAND_SPLIT);
		if(commands.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		String firstCommands[] = commands[0].split(ImportAndExportMoveList.COMMAND_INFO_SPLIT);
		if(firstCommands.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		String secondCommands[] = commands[1].split(ImportAndExportMoveList.COMMAND_INFO_SPLIT);
		Command commandOne = breakdownCommands(firstCommands[0],firstCommands[1]);
		if (commandOne == null){
			return null;
		}
		Command commandTwo = null;
		commandOne.execute();
		if(secondCommands.length == 2){
			commandTwo = breakdownCommands(secondCommands[0],secondCommands[1]);
		}else{
			commandTwo = new NoSecondaryCommand();
		}	
		if (commandTwo == null){
			return null;
		}
		commandTwo.execute();
		commandOne.addSecondaryCommand(commandTwo);
		if(players.size() == 2){
			if(currentPlayer.equals(players.get(0))){
				currentPlayer = players.get(1);
			}else{
				currentPlayer = players.get(0);
			}
		}
		return commandOne;
		
	}
	/**
	 * This method will select which command is the correct command from the command string.
	 * It will also parse the action string and return the command object
	 * @param command The string representing the command
	 * @param action The string representing the action
	 * @return Returns the command created from parsing. Will return null if there is an error
	 */
	private Command breakdownCommands(String command, String action){
		try {
			if(command.equals(ImportAndExportMoveList.ELEMENT_ATTACK)){
				return parseAttackCommand(action);
			}else if(command.equals(ImportAndExportMoveList.ELEMENT_CASTLE)){
				return parseCastleCommand(action);
			}else if(command.equals(ImportAndExportMoveList.ELEMENT_ENPASSANT)){
				return parseEnPassantAttackCommand(action);
			}else if(command.equals(ImportAndExportMoveList.ELEMENT_MOVE)){
				return parseMoveCommand(action);
			}else if(command.equals(ImportAndExportMoveList.ELEMENT_NO_COMMAND)){
				return parseNoSecondaryCommand(action);
			}else if(command.equals(ImportAndExportMoveList.ELEMENT_PROMOTE)){
				return parsePromoteCommand(action);
			}else if (command.equals(ImportAndExportMoveList.ELEMENT_PLACE)){
				return parsePlacePieceCommand(action);
			}else if (command.equals(ImportAndExportMoveList.ELEMENT_KNIGHT_MOVE)){
				return parseKnightsTourMoveCommand(action);
			}
		} catch (Exception e) {
			errorCode = COMMAND_FORMAT_ERROR;
			return null;
		}
		return null;
	}
	/**
	 * This method will parse the attack command
	 * @param line The line to be parsed
	 * @return Returns the attack command object. Returns null if there was an error
	 */
	private AttackCommand parseAttackCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		Point pointTwo = parseCoordinate(points[1]);
		if(pointOne == null || pointTwo == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());
		Tile tileTwo = board.getTile((int)pointTwo.getX(), (int)pointTwo.getY());
		return new AttackCommand(tileOne,tileTwo,currentPlayer);
	}
	/**
	 * This method will parse the castle command
	 * @param line The line to be parsed
	 * @return Returns the castle command object. Returns null if there was an error
	 */
	private CastleCommand parseCastleCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		Point pointTwo = parseCoordinate(points[1]);
		if(pointOne == null || pointTwo == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());
		Tile tileTwo = board.getTile((int)pointTwo.getX(), (int)pointTwo.getY());
		return new CastleCommand(tileOne,tileTwo);
	}
	/**
	 * This method will parse the enpassant attack command
	 * @param line The line to be parsed
	 * @return Returns the enpassant attack command object. Returns null if there was an error
	 */
	private EnPassantAttackCommand parseEnPassantAttackCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		Point pointTwo = parseCoordinate(points[1]);
		if(pointOne == null || pointTwo == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());
		Tile tileTwo = board.getTile((int)pointTwo.getX(), (int)pointTwo.getY());
		return new EnPassantAttackCommand(tileOne,tileTwo,currentPlayer);
	}
	/**
	 * This method will parse the move command
	 * @param line The line to be parsed
	 * @return Returns the move command object. Returns null if there was an error
	 */
	private MoveCommand parseMoveCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		Point pointTwo = parseCoordinate(points[1]);
		if(pointOne == null || pointTwo == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());
		Tile tileTwo = board.getTile((int)pointTwo.getX(), (int)pointTwo.getY());
		return new MoveCommand(tileOne,tileTwo);
	}
	/**
	 * This method will parse the no secondary command
	 * @param line The line to be parsed
	 * @return Returns the no secondary command object. Returns null if there was an error
	 */
	private NoSecondaryCommand parseNoSecondaryCommand(String line){
		return new NoSecondaryCommand();
	}
	private PromoteCommand parsePromoteCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		if(pointOne == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());		
		return new PromoteCommand(points[1],tileOne);
	}
	private PiecePlaceCommand parsePlacePieceCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		if(pointOne == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());		
		return new PiecePlaceCommand(tileOne,points[1],currentPlayer);
	}
	private KnightsTourMoveCommand parseKnightsTourMoveCommand(String line){
		String points[] = line.split(ImportAndExportMoveList.INFO_ACTION_SPLIT);
		if(points.length != 2){
			errorCode = REGEX_MISSING_ERROR;
			return null;
		}
		Point pointOne = parseCoordinate(points[0]);
		Point pointTwo = parseCoordinate(points[1]);
		if(pointOne == null || pointTwo == null){
			return null;
		}
		Tile tileOne = board.getTile((int)pointOne.getX(), (int)pointOne.getY());
		Tile tileTwo = board.getTile((int)pointTwo.getX(), (int)pointTwo.getY());
		return new KnightsTourMoveCommand(tileOne,tileTwo);
	}
	/**
	 * This method will return a point object by parsing a string
	 * @param coordiante The string representation of the coordinate
 	 * @return Returns a point object of the coordinate. Returns null if there was an error
	 */
	private Point parseCoordinate(String coordiante){
		int x = 0;
		int y = 0;
		try {
			String points[] = coordiante.split(",");
			if(points.length != 2){
				errorCode = REGEX_MISSING_ERROR;
				return null;
			}
			int index = points[0].indexOf("(");
			x = Integer.parseInt(points[0].substring(index + 1));
			index = points[1].indexOf(")");
			y = Integer.parseInt(points[1].substring(index - 1,index));
		} catch (NumberFormatException e) {
			errorCode = POINT_PARSE_ERROR;
			return null;
		}
		return new Point(x,y);
	}
	/**
	 * This method will set the extension of the files to be imported or exported
	 */
	@Override
	public void setExtension() {
		chessExtension = EXTENSION;
	}
}
